package com.upb.webserver.chain;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import com.upb.webserver.chainorg.ChainOrgManager;
import com.upb.webserver.common.enums.*;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.order.ReqAddWorkOrderOfNewChainVO;
import com.upb.webserver.common.properties.WebServerProperties;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.JsonUtils;
import com.upb.webserver.common.tools.MybatisExampleTools;
import com.upb.webserver.dao.entity.*;
import com.upb.webserver.dao.mapper.*;
import com.upb.webserver.message.MessageService;
import com.upb.webserver.order.WorkOrderManager;
import com.upb.webserver.org.OrgManager;
import com.upb.webserver.org.OrgService;
import com.upb.webserver.rest.fisco.FiscoChainMgrRestService;
import com.upb.webserver.rest.fisco.FiscoStatRestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
public class ChainManager {
    @Autowired
    private OrgService orgService;
    @Autowired
    private ChainOrgManager chainOrgManager;
    @Autowired
    private MessageService messageService;
    @Autowired
    private TbChainMapper chainMapper;
    @Autowired
    private WorkOrderManager workOrderManager;
    @Autowired
    private WebServerProperties webServerProperties;
    @Autowired
    private OrgManager orgManager;
    @Autowired
    private TbChainOrgMapper chainOrgMapper;
    @Autowired
    private TbApplicationMapper applicationMapper;
    @Autowired
    private TbApplicationOrgMapper applicationOrgMapper;
    @Autowired
    private TbDeveloperAppMapMapper developerAppMapMapper;
    @Autowired
    private TbWorkOrderMapper orderMapper;
    @Autowired
    private TbChainHostMapper chainHostMapper;
    @Autowired
    private FiscoChainMgrRestService fiscoChainMgrRestService;
    @Autowired
    private FiscoStatRestService fiscoStatRestService;


    /**
     * @param platform
     * @param creatorId
     * @param chainName
     * @param version
     * @param chainType
     * @return
     */
    @Transactional
    public TbChain saveNewChain(Byte platform, int creatorId, String chainName, String version, ChainTypeEnum chainType, Byte encryptionId, String description) {
        log.info("start exec method [saveNewChain]. platform:{} creatorId:{} chainName:{} version:{} chainType:{} encryptionId:{}", platform, creatorId, chainName, version, chainType.getId(), encryptionId);

        //check record
        TbChain chain = chainMapper.selectByChainName(chainName);
        log.info("query chain by name:{},the result:{}", chainName, Objects.isNull(chain) ? "null" : JsonUtils.objToString(chain));

        //add
        if (Objects.isNull(chain)) {
            log.info("not found record by chainName:{} ,add new one to db", chainName);
            TbChain chainOnDb = new TbChain();
            chainOnDb.setName(chainName);
            chainOnDb.setVersion(version);
            chainOnDb.setChainType(chainType.getId());
            chainOnDb.setEncryptionId(encryptionId);
            chainOnDb.setCreateStatus(ChainOrAppStatusEnum.ALREADY_TO_INITIALIZED.getId());
            chainOnDb.setCreateFromPlatform(platform);
            chainOnDb.setCreateBy(creatorId);
            chainOnDb.setGmtCreate(new Date());
            chainOnDb.setGmtModified(new Date());
            chainOnDb.setDescription(description);

            //insert
            chainMapper.insertSelective(chainOnDb);
            return chainMapper.selectByPrimaryKey(chainOnDb.getId());
        }

        //update
        if (chain.getCreateStatus() == ChainOrAppStatusEnum.DEPLOY_FAIL.getId()) {
            log.info("chain:{} already exist,and old status is:{},update new status to:{}", chainName, chain.getCreateStatus(), ChainOrAppStatusEnum.ALREADY_TO_INITIALIZED.getId());
            chain.setCreateStatus(ChainOrAppStatusEnum.ALREADY_TO_INITIALIZED.getId());
            chain.setGmtModified(new Date());
            chainMapper.updateByPrimaryKey(chain);
            return chain;
        }

        log.warn("fail exec method [saveNewChain]. found record by name:{} ,    and the chain status is:{}", chainName, chain.getCreateStatus());
        throw new WebServerException(ConstantCode.NOT_SUPPORT_OPERATE);
    }


    /**
     * @param chainType
     * @param newName
     */
    public void checkBeforeAddNewChain(Byte chainType, String newName) {
        log.info("start exec method [checkBeforeAddNewChain]. chainType:{} newName:{}", chainType, newName);

        if (!webServerProperties.getSupportChainTypeList().contains(chainType)) {
            log.warn("fail exec method [checkBeforeAddNewChain]. not support chainType:{} ,just support:{}", chainType, JsonUtils.objToString(webServerProperties.getSupportChainTypeList()));
            throw new WebServerException(ConstantCode.CHAIN_TYPE_NOT_SUPPORT);
        }

        //check name
        if (chainMapper.selectByChainName(newName) != null) {
            log.warn("unsuccessful exec method [checkBeforeAddNewChain]. the name:{} has bean use", newName);
            throw new WebServerException(ConstantCode.CHAIN_NAME_HASH_BEAN_USE);
        }
        List<TbWorkOrder> workOrderList = workOrderManager.listWorkOrderWhichHadNotBeanHandleSuccess(Arrays.asList(OrderTypeEnum.NEW_CHAIN_FABRIC, OrderTypeEnum.NEW_CHAIN_FISCO));
        for (TbWorkOrder workOrder : workOrderList) {
            ReqAddWorkOrderOfNewChainVO chainDetailVO = JsonUtils.stringToObj(workOrder.getDetail(), ReqAddWorkOrderOfNewChainVO.class);
            if (chainDetailVO.getName() == newName) {
                log.warn("unsuccessful exec method [checkBeforeAddNewChain]. found chainName:{} on workOrder:{}", newName, workOrder.getId());
                throw new WebServerException(ConstantCode.CHAIN_NAME_HASH_BEAN_USE);
            }
        }
    }


    /**
     * @param chainName
     * @return
     */
    public Boolean wasChainDeployedFail(String chainName) {
        log.info("start exec method [wasChainDeployedFail] , chainName:{}", chainName);
        TbChain chain = chainMapper.selectByChainName(chainName);
        Boolean wasDeployedFail = false;
        if (chain != null && chain.getCreateStatus() == ChainOrAppStatusEnum.DEPLOY_FAIL.getId()) {
            log.info("found chain record by chainName:{} ,and the status is:{}", chainName, chain.getCreateStatus());
            wasDeployedFail = true;
        }

        log.info("success exec method [wasChainDeployedFail] , chainName:{} wasDeployedFail:{}", chainName, wasDeployedFail);
        return wasDeployedFail;
    }

    /**
     * @param chainIdList
     * @param status
     * @return
     */
    public long countByChainIdListAndStatus(List<Integer> chainIdList, byte status) {
        log.info("start exec method [countByChainIdListAndStatus] , chainIdList:{} status:{}", JsonUtils.objToString(chainIdList), status);
        if (CollectionUtil.isEmpty(chainIdList)) return 0;
        Map<String, Object> inParam = MapUtil.of("id", chainIdList);
        TbChainExample example = MybatisExampleTools.initSampleExample(TbChainExample.class, new TbChain(status), inParam);
        long count = chainMapper.countByExample(example);
        log.info("success exec method [countByChainIdListAndStatus] , result:{}", count);
        return count;
    }


    /**
     * @param chainId
     * @return
     */
    public TbChain verifyChainId(int chainId) {
        log.info("start exec method [verifyChainId]. chainId:{}", chainId);
        TbChain tbChain = chainMapper.selectByPrimaryKey(chainId);
        if (Objects.isNull(tbChain)) {
            log.warn("fail exec method [verifyChainId]. not found record by chainId:{}", chainId);
            throw new WebServerException(ConstantCode.INVALID_CHAIN_ID);
        }
        log.info("success exec method [verifyChainId]. result:{}", JsonUtils.objToString(tbChain));
        return tbChain;
    }

    /**
     * @param idSet
     * @return
     */
    public List<TbChain> getAllChainByIdSet(Set<Integer> idSet) {
        log.info("start exec method [getAllChainByIdSet]. idSet:{}", JsonUtils.objToString(idSet));
        if (CollectionUtil.isEmpty(idSet)) {
            log.info("finish exec method[getAllChainByIdSet], idSet is empty");
            return new ArrayList<>();
        }

        //example
        TbChainExample example = new TbChainExample();
        TbChainExample.Criteria criteria = example.createCriteria();
        criteria.andIdIn(CollectionUtil.newArrayList(idSet));

        // query chain list
        List<TbChain> chainList = chainMapper.selectByExampleWithBLOBs(example);
        log.info("success exec method [getAllChainByIdSet]. idSet:{} result:{}", JsonUtils.objToString(idSet), JsonUtils.objToString(chainList));
        return chainList;
    }


    /**
     * @param platform
     * @param creatorAccountId
     * @return
     */
    public long countChainByCreator(PlatformEnum platform, int creatorAccountId) {
        log.debug("start exec method [countChainByCreator]. platform:{} creatorAccountId:{}", platform, creatorAccountId);
        TbChainExample example = MybatisExampleTools.initSampleExample(TbChainExample.class, new TbChain(platform, creatorAccountId));
        long count = chainMapper.countByExample(example);
        log.debug("success exec method [countChainByCreator]. platform:{} creatorAccountId:{} count:{}", platform, creatorAccountId, count);
        return count;
    }


    /**
     * @param companyIdList
     * @return
     */
    public Set<Integer> getChainIdSetByCompanyIdList(List<Integer> companyIdList) {
        log.info("start exec method [getChainIdSetByCompanyId]. companyIdList:{}", JsonUtils.objToString(companyIdList));
        //get orgList by companyId
        List<TbOrg> orgList = orgManager.queryOrgByCompanyIdList(companyIdList);
        if (CollectionUtil.isEmpty(orgList)) {
            log.info("finish exec method [getChainIdSetByCompanyId]. not found org by companyIdList:{}", JsonUtils.objToString(companyIdList));
            return CollectionUtil.newHashSet();
        }
        List<Integer> orgIdList = orgList.stream().map(o -> o.getId()).collect(Collectors.toList());

        //get applicationId
        List<TbChainOrg> chainOrgMapList = chainOrgMapper.selectByOrgIdList(orgIdList);
        if (CollectionUtil.isEmpty(chainOrgMapList)) {
            log.info("finish exec method [getChainIdSetByCompanyId]. not found record from tb_chain_org by orgIdList:{}", JsonUtils.objToString(orgIdList));
            return CollectionUtil.newHashSet();
        }

        Set<Integer> chainIdSet = chainOrgMapList.stream().map(ao -> ao.getChainId()).collect(Collectors.toSet());
        log.info("success exec method [getChainIdSetByCompanyId]. result:{}", JsonUtils.objToString(chainIdSet));
        return chainIdSet;
    }
}
